import json
import os
from typing import Dict, List, Optional
from zhipuai import ZhipuAI
from .error_handler import ErrorHandler

class GLMAnalyzer:
    def __init__(self, api_key: str, categories: Dict, model: str = None, base_url: Optional[str] = None):
        self.api_key = api_key
        self.categories = categories
        self.model = model or os.getenv('GLM_MODEL', 'glm-4v')
        self.base_url = base_url
        self.client = ZhipuAI(api_key=self.api_key, base_url=self.base_url) if self.base_url else ZhipuAI(api_key=self.api_key)
        self.error_handler = ErrorHandler()
        # configurable analysis parameters
        self.max_tokens = int(os.getenv('GLM_MAX_TOKENS', '512'))
        self.compact_prompt = str(os.getenv('GLM_COMPACT_PROMPT', '0')).lower() in ('1','true','yes')

    def build_analysis_prompt(self, video_title: str = "", for_model: Optional[str] = None) -> str:
        def dump(name):
            return json.dumps(self.categories.get(name, {}), ensure_ascii=False, indent=2)
        def list_option_names(name):
            return list(self.categories.get(name, {}).keys())
        title_line = f"视频标题：{video_title}" if video_title else "注意：没有提供视频标题，请根据视频内容推断标题相关特征。"
        # GLM-4.1V-Thinking-Flash 专用：严格纯文本字段，禁止思考过程
        if for_model == 'GLM-4.1V-Thinking-Flash':
            duration_opts = "、".join(list_option_names('视频时长') or ['≤1分钟','1-5分钟','5-30分钟','>30分钟'])
            theme_opts = "、".join(list_option_names('视频主题'))
            people_opts = "、".join(list_option_names('出境人物'))
            av_opts = "、".join(list_option_names('视听符号'))
            tone_opts = "、".join(list_option_names('感情色彩'))
            style_opts = "、".join(list_option_names('语言风格'))
            title_emotion_opts = "、".join(list_option_names('标题情感表达'))
            topic_opts = "、".join(list_option_names('标题平台话题'))
            return "\n".join([
                "你是短视频内容标注助手。只输出以下9行字段，严格遵循格式，不得输出任何分析、解释、思考或额外文字。",
                title_line,
                f"视频时长: {duration_opts} 中选择一项",
                f"视频主题: {theme_opts} 中选择一项",
                f"出境人物: {people_opts} 中选择一项",
                f"视听符号: 按此字典格式输出：{'{'}'场景符号': '选择一项', '音乐符号': '选择一项', '字幕符号': '选择一项或无'{'}'}；所有取值从 {av_opts} 中选择",
                f"感情色彩: {tone_opts} 中选择一项",
                f"语言风格: {style_opts} 中选择一项",
                f"标题情感表达: {title_emotion_opts} 中选择一项",
                f"标题平台话题: {topic_opts} 中选择一项",
                "视频摘要: 用100-200字中文客观概述视频内容",
                "输出规则：",
                "- 仅输出上述9行，每行严格为“字段名: 值”格式；值只能是一个选项或上述字典，不可包含任何解释性文字。",
                "- 禁止出现思考用语（如：可能、因为、推断、猜测、疑问句等），禁止使用问号或括号。",
                "- 不得输出JSON、代码块、列表或任何多余内容。",
                "- 无法确定时选择最可能的单一选项，不得输出“未知”。",
                "- 示例：视听符号: {'{'}'场景符号': '果园', '音乐符号': '背景音乐', '字幕符号': '字幕'{'}'}",
            ])
        if self.compact_prompt:
            # compact: list category options without keywords to reduce tokens
            lines = ["请基于提供的帧图像，按以下字段输出结构化结果（每字段仅选择一个最合适的选项），并生成100-200字摘要。"]
            for cat, options in self.categories.items():
                option_names = list(options.keys())
                lines.append(f"{cat}：{ '、'.join(option_names) }")
            lines.append(title_line)
            lines.append("请以JSON格式输出：视频时长、视频主题、出境人物、视听符号、感情色彩、语言风格、标题情感表达、标题平台话题、视频摘要。")
            return "\n".join(lines)
        prompt = f"""
 请分析提供的抖音视频内容，并根据以下分类标准输出结构化数据。请确保每个字段只选择一个最合适的选项。

 视频时长分类标准：
 {dump('视频时长')}

 视频主题分类标准：
 {dump('视频主题')}

 出境人物分类标准：
 {dump('出境人物')}

 视听符号分类标准：
 {dump('视听符号')}

 感情色彩分类标准：
 {dump('感情色彩')}

 语言风格分类标准：
 {dump('语言风格')}

 标题情感表达分类标准：
 {dump('标题情感表达')}

 标题平台话题分类标准：
 {dump('标题平台话题')}

 {title_line}

 请严格按照上述分类标准进行分析，每个字段只能选择对应的一个选项。
 同时请生成一个简洁的视频内容摘要（100-200字）。

 请以JSON格式输出结果，包含以下字段：
 - 视频时长
 - 视频主题
 - 出境人物
 - 视听符号
 - 感情色彩
 - 语言风格
 - 标题情感表达
 - 标题平台话题
 - 视频摘要
 """
        return prompt

    def analyze_video_content(self, frames: List[str], title: str = "") -> Dict:
        # 根据用户指定模型优先级进行回退：优先 self.model，其次其它候选
        default_candidates = ['glm-4v', 'glm-4.5v', 'GLM-4.1V-Thinking-Flash']
        if self.model in default_candidates:
            candidates = [self.model] + [m for m in default_candidates if m != self.model]
        else:
            candidates = default_candidates
        last_err = None
        for m in candidates:
            prompt = self.build_analysis_prompt(title, for_model=m)
            messages = [{
                "role": "user",
                "content": [{"type": "text", "text": prompt}] + [
                    {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{f}"}} for f in frames
                ]
            }]
            try:
                print(f"[GLM] 请求模型: {m} | 帧数: {len(frames)} | 提示词字数: {len(prompt)} | max_tokens={self.max_tokens}")
                resp = self.client.chat.completions.create(model=m, messages=messages, max_tokens=self.max_tokens)
                text = resp.choices[0].message.content
                print(f"[GLM] 响应长度: {len(text)} 字符")
                return self.parse_glm_response(text)
            except Exception as e:
                # 记录失败并继续尝试下一个模型
                self.error_handler.log_error(f"[GLM][{m}]", e, 'GLMError')
                print(f"[GLM] 模型失败: {m} -> {e}")
                last_err = e
                continue
        # 所有模型均失败
        raise RuntimeError(f'GLM 模型全部失败：{last_err}')

    def parse_glm_response(self, response_text: str) -> Dict:
        try:
            if '```json' in response_text:
                json_str = response_text.split('```json')[1].split('```')[0]
            elif '```' in response_text:
                json_str = response_text.split('```')[1]
            else:
                json_str = response_text
            return json.loads(json_str.strip())
        except Exception as e:
            print(f"[GLM] 解析失败，转手动提取: {e}")
            return self._extract_fields_manually(response_text)

    def _extract_fields_manually(self, text: str) -> Dict:
        """当模型未按要求输出时，严格按类目表选项规范化提取值。"""
        def select_by_option_names(field: str, raw: str) -> Optional[str]:
            opts = list(self.categories.get(field, {}).keys())
            for o in opts:
                if o and o in raw:
                    return o
            return None
        def select_by_keywords(field: str, raw: str) -> Optional[str]:
            # 使用类目关键词做匹配
            for opt, kws in self.categories.get(field, {}).items():
                for kw in kws:
                    if kw and kw in raw:
                        return opt
            return None
        def normalize_single(field: str, raw: str, default_val: str) -> str:
            # 先按选项名匹配；若无，再按关键词；仍无，则默认
            return select_by_option_names(field, raw) or select_by_keywords(field, raw) or default_val

        result: Dict[str, str] = {}
        fields = ['视频时长','视频主题','出境人物','视听符号','感情色彩','语言风格','标题情感表达','标题平台话题','视频摘要']
        # 粗提取每一行的原始文本
        raw_map: Dict[str, str] = {}
        for field in fields:
            if field in text:
                start = text.find(field) + len(field) + 1
                end = text.find('\n', start)
                if end == -1:
                    end = len(text)
                raw_map[field] = text[start:end].strip(' :')
        # 规范化各字段
        if '视频时长' in raw_map:
            result['视频时长'] = normalize_single('视频时长', raw_map['视频时长'], '≤1分钟')
        if '视频主题' in raw_map:
            # 若文本出现“采摘/果园/耕种/收割”等词，优先归为农事劳作类
            theme_raw = raw_map['视频主题']
            hints = ['采摘','果园','耕种','农忙','收割','田间']
            if any(h in theme_raw for h in hints):
                result['视频主题'] = '农事劳作类'
            else:
                result['视频主题'] = normalize_single('视频主题', theme_raw, '乡村生活类')
        if '出境人物' in raw_map:
            people_raw = raw_map['出境人物']
            if '邻里' in people_raw or '乡亲' in people_raw or '多人' in people_raw:
                result['出境人物'] = '邻里乡亲'
            elif '家人' in people_raw:
                result['出境人物'] = '家人'
            elif '本人' in people_raw:
                result['出境人物'] = '创业青年本人'
            else:
                result['出境人物'] = normalize_single('出境人物', people_raw, '无明确人物')
        if '视听符号' in raw_map:
            av_raw = raw_map['视听符号']
            sv = self.categories.get('视听符号', {})
            # 场景符号选择
            scene_kw = sv.get('场景符号', [])
            scene_val = None
            for kw in scene_kw:
                if kw in av_raw:
                    scene_val = kw
                    break
            scene_val = scene_val or '农田'
            # 音乐符号选择
            music_kw = sv.get('音乐符号', [])
            music_val = None
            for kw in music_kw:
                if kw in av_raw:
                    music_val = kw
                    break
            music_val = music_val or '背景音乐'
            # 字幕符号选择（类目无该项，用启发式）
            subtitle_val = '字幕' if ('字幕' in av_raw or '文案' in av_raw) else '无'
            result['视听符号'] = {
                '场景符号': scene_val,
                '音乐符号': music_val,
                '字幕符号': subtitle_val
            }
        if '感情色彩' in raw_map:
            tone_raw = raw_map['感情色彩']
            if '正面' in tone_raw:
                result['感情色彩'] = '正面'
            elif '负面' in tone_raw:
                result['感情色彩'] = '负面'
            else:
                result['感情色彩'] = normalize_single('感情色彩', tone_raw, '中性')
        if '语言风格' in raw_map:
            style_raw = raw_map['语言风格']
            # 捕捉常见词
            if '亲切' in style_raw:
                result['语言风格'] = '亲切'
            elif '幽默' in style_raw:
                result['语言风格'] = '幽默'
            elif '煽情' in style_raw:
                result['语言风格'] = '煽情'
            elif '平淡' in style_raw:
                result['语言风格'] = '平淡'
            elif '欢快' in style_raw:
                result['语言风格'] = '欢快'
            else:
                result['语言风格'] = normalize_single('语言风格', style_raw, '无明显风格')
        if '标题情感表达' in raw_map:
            te_raw = raw_map['标题情感表达']
            if '无情感' in te_raw:
                result['标题情感表达'] = '无情感化表达'
            else:
                result['标题情感表达'] = '有情感化表达' if '有情感' in te_raw or '情感化' in te_raw else normalize_single('标题情感表达', te_raw, '无情感化表达')
        if '标题平台话题' in raw_map:
            tp_raw = raw_map['标题平台话题']
            if '添加话题' in tp_raw or '#话题' in tp_raw or '#' in tp_raw:
                result['标题平台话题'] = '添加话题'
            else:
                result['标题平台话题'] = normalize_single('标题平台话题', tp_raw, '未添加话题')
        if '视频摘要' in raw_map:
            # 摘要保留原文本，但去掉疑问句痕迹
            summary = raw_map['视频摘要']
            summary = summary.replace('？', '').replace('可能', '').replace('猜测', '').replace('推断', '')
            result['视频摘要'] = summary
        return result





